home *** CD-ROM | disk | FTP | other *** search
- /*
- Timer.c
-
- An interval timer based on Apple's Time Manager. It returns the time, in
- seconds, that elapsed between calling StartTimer() and PeekTimerSecs() or
- StopTimerSecs(). The timing of a single interval (from StartTimer to PeekTimer,
- PeekTimerSecs, StopTimer, or StopTimerSecs) is always very accurate (better than
- 1 part in 1000 relative to a good time standard) and a precision of better than
- 200 µs. Under System 7, which has the Extended Time Manager, (except on the
- Radius Rocket) this accuracy applies to all intervals; measuring splits by
- peeking has no effect on the accuracy of subsequent times. Under System 6 (or on
- the Radius Rocket even under System 7) each peek will lose a variable amount of
- time, compromising accuracy of subsequent times from that timer, due to
- documented deficiencies of Apple's old Revised Time Manager. The even older
- "Standard" Time Manager is not supported here, and will result in an error
- message. Note that you are allowed an unlimited number of timers that each
- operate independently. Peeking one timer has no effect on the rest of your
- timers.
-
- Timer *NewTimer(void);
- void DisposeTimer(Timer *t);
- void StartTimer(Timer *t);
- double PeekTimerSecs(Timer *t); // s, with µs precision and no time limit
- double StopTimerSecs(Timer *t); // s, with µs precision and no time limit
- long PeekTimer(Timer *t); // µs, up to 36 minutes
- long StopTimer(Timer *t); // µs, up to 36 minutes
-
- Timer *timer;
- long t;
-
- timer=NewTimer();
- StartTimer(timer);
- do(i=0;i<100;i++);
- t=StopTimer(timer);
- DisposeTimer(timer);
- printf("One hundred iterations takes %ld µs\n",t);
-
- The timing result comes in two flavors. StopTimerSecs() and PeekTimerSecs() both
- return the time in secs as a double, and can time an interval of essentially
- unlimited duration with 200 µs precision. StopTimer() and PeekTimer() return the
- time in microseconds (with same 200 µs precision) as a long, which can hold a
- time up to 2,147,483,647 µs, which is nearly 36 minutes. I generally prefer
- PeekTimerSecs, because it's foolproof (won't overflow), even though it's a bit
- slower than PeekTimer. But if you don't have a floating point unit, then
- PeekTimerSecs is MUCH slower than PeekTimer.
-
- You can have an unlimited number of Timers running at once. The only restriction
- is that you must create each Timer, by calling NewTimer() before you use it,
- and, obviously, should not use it after calling DisposeTimer().
-
- The Time Manager seems to use interrupts at a higher rate than the 10 s interval
- I requested here (which would never expire in typical use). I infer this from
- the fact that disabling interrupts by SetPriority(7) greatly reduces the timing
- value returned by StopTimer(). So don't disable interrupts while you're timing.
-
- StopTimer() adds a small offset (about 62 µs on a Mac IIci) to the raw time in
- order to return an unbiased estimate of the time from when StartTime was called
- to when StopTime() was called. This is the most useful time measure for
- measuring the interval between two events (e.g. video frames). Alternatively, if
- you wish to measure processing time, then it is more useful to compute the time
- from when StartTimer() returns to when StopTimer() is called. For this you
- simply subtract the fixed duration of StartTimer(), call-to-return, which is
- provided in µs in your Timer structure in the long structure member
- "timeToStartTimer".
-
- betweenTime = StopTimer(t) - t->timeToStartTimer;
-
- When you use StopTimerSecs() you'd have to divide timeToStartTimer by a
- million to convert µs to s. For some purposes this offset is negligibly small.
- On my Mac IIci the timeToStartTimer is 240 µs. This offset, and the one
- mentioned above are measured automatically the first time you call NewTimer().
- Alternatively, instead of having to remember the name of the structure member,
- just measure the timeToStartTimer yourself and subtract it from all subsequent
- measures:
-
- StartTimer(t);
- s0=StopTimerSec(t);
-
- The Timer structures are kept in a linked list so that KillEveryTimer(), which
- is placed in the _atexit() queue, will find and kill them when the application
- exits.
-
- TEST PROGRAM:
-
- Measure the same interval in three different ways. All three answers agree, at
- least under System 7. Note that timer2 is repeatedly peeked at, while "timer" is
- uninterrupted. This checks for any side effects of peeking. (There will be a
- substantial loss of time due to peeking if you run without the Extended Time Mgr,
- i.e. under System 6 or on Radius Rocket.)
-
- void main(void)
- {
- double s,s2;
- long ticks,m,m2;
- int i,n=1000;
- Timer *timer,*timer2;
-
- printf("We'll simultaneously measure a single time interval in 3 ways:\n"
- "1. by just starting and stopping the timer\n"
- "2. by starting the timer and repeatedly peeking at it %d times\n"
- "3. by using TickCount(), which runs at 60.15 Hz.\n...\n",n);
- timer=NewTimer();
- timer2=NewTimer();
- Delay(1,&ticks); // synchronize ourselves to the tick counter for better accuracy.
- StartTimer(timer);
- StartTimer(timer2);
- for(i=0;i<n;i++)s2=PeekTimerSecs(timer2); // time PeekTimerSecs()
- ticks=TickCount()-ticks;
- s=StopTimerSecs(timer);
- s2=StopTimerSecs(timer2);
- printf("The interval was:\n1. %4.0f ms\n2. %4.0f ms\n3. %4.0f±%.0f ms\n\n"
- ,s*1e3,s2*1e3,1e3*(ticks+0.5)/60.15,1e3*0.5/60.15);
- printf("Each call to PeekTimerSecs() takes %4.0f µs, and loses %4.0f µs.\n"
- ,s*1e6/n,(s-s2)*1e6/n);
- StartTimer(timer);
- StartTimer(timer2);
- for(i=0;i<n;i++)m2=PeekTimer(timer2); // time PeekTimer()
- m=StopTimer(timer);
- m2=StopTimer(timer2);
- printf("Each call to PeekTimer() takes %4ld µs, and loses %4ld µs.\n"
- ,m/n,(m-m2)/n);
- DisposeTimer(timer);
- DisposeTimer(timer2);
- }
-
- ACKNOWLEDGMENT:
- David Brainard wrote the first draft of PeekTimer().
-
- APPLE DOCUMENTION ERRORS:
- There are two errors in the Time Manager chapter of "New Inside
- Macintosh: Processes". One of the errors is particularly relevant to the
- PowerPC. Both errors are on page 3-8.
-
- 1. The manual says, "You should set tmWakeUp and tmReserved to 0 when you first
- install an extended Time Manager task." That's correct, but it should go on to
- say that "You should set tmReserved to 0 before each and every call to
- InsXTime."
-
- It took me a long time to discover this. Some computers, e.g. my PowerBook 170,
- don't care. But, e.g. the PowerMac 6100, does care. It increments tmReserved
- each time a task is installed by InsXTime. If tmReserved reaches 128 then the
- Time Manager goes haywire. Conversely, everything runs fine on every Mac I've
- tested if tmReserved is zeroed before each call to InsXTime.
-
- 2. The manual says "The tmWakeUp field contains the time at which the Time
- Manager task specified by tmAddr was last executed (or 0 if it has not yet been
- executed)." THIS IS WRONG AND MISLEADING! It should say, instead, "The tmWakeUp
- field contains the time at which the Time Manager task specified by tmAddr is
- scheduled to be executed (or 0 if it has not yet been scheduled)."
-
- RADIUS ROCKET BUG:
- This applies to my Radius Rocket in my Mac II using RocketWare 1.5 running under
- System 7.1 with System Update.
-
- Apparently Radius decided not to implement the Extended Time Manager and only
- went so far as diverting the InsXTime trap to InsTime. A minimal fix would be to
- at least give honest information by making Gestalt correctly report the Time
- Manager version as gestaltRevisedTimeMgr instead of falsely claiming it's
- gestaltExtendedTimeMgr. My programs check for this, and I spent a whole day
- debugging crashes on my Radius due to this misleading information from Gestalt.
-
- According to Gestalt, the Extended Time Manager is present, but in fact the
- InsXTime trap is only providing the service expected from the InsTime trap of
- the Revised Time Manager. Specifically, in the program below, when we call
- InsXTime the second time, with a nonzero tmWakeUp, we are supposed to resume the
- previous interval, not start a new one. Since the original interval was 100 s,
- there should still be lots of time left in it, and when we call RmvTime for the
- last time tmCount should be nonzero. This program fails only on my Radius
- Rocket. It runs fine on my Mac II, IIci, IIfx, LC475, Quadra 840av,
- PowerBook 170, and PowerMac.
-
- UNDERSTANDING THE CODE OF TIMER.C:
- There are a few subtleties to understanding the code. Firstly, you should read
- the manual, i.e. the Time Manager chapter of New Inside Mac: Processes. If the
- Extended Time Manager is available, PeekTimer takes advantage of its ability to
- resume the interrupted interval, so the splits (from PeekTimer and
- PeekTimerSecs) won't have any effect on the accuracy of subsequent times.
- However, there is a tricky case that requires special handling. If we are very
- close to the end of the current interval then the interval might expire before
- we have a chance to call PrimeTime(). As explained in the manual on page 3-8
- this would amount to an impossible request for a task execution time in the
- past. When that happens the Time Mgr changes the the requested time to the
- present, which would destroy our synchrony. Therefore PeekTimer checks that
- there is at least 10 ms left in the interrupted interval, and, if not, then
- advances to the next interval, incrementing the counters appropriately as though
- our timer task had been called. This transparently preserves accuracy.
-
- HISTORY:
- 8/19/92 dgp based on Time Manager chapter in Inside Mac VI and TimeIt.c,
- which is now obsolete. I also benefited from examining code by Jonothan Kolodny
- forwarded to me by Thomas Busey.
- 8/27/92 dgp Rewrote everything. Made the interrupt service routine reentrant
- by eliminating all use of global variables, using only the structure pointed
- to by A1. There can now be an unlimited number of timers active at once.
- Added NewTimer() and DisposeTimer() to manage them.
- 9/10/92 dgp added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
- Memory book this isn't strictly necessary, since Time Manager tasks will be
- called only when it's safe.
- 1/11/93 dgp StopTimerSecs() now returns NAN if called with a NULL pointer.
- 7/9/93 dgp Test MATLAB in if() instead of #if.
- 5/28/94 dgp Made compatible with Apple's Universal Headers. Thanks to Bob Dougherty
- (wolfgang@cats.ucsc.edu) for reporting the incompatibility.
- 9/7/94 dgp Made declaration of TimerTask--how it receives the argument ptr--conditional on PowerPC.
- 10/22/94 dgp Added PeekTimer, written by David Brainard, and enhanced it to used the Extended
- Time Mgr's facility of resuming an interrupted interval. Modularized all the code somewhat,
- eliminating duplications.
- 10/24/94 dgp Made declaration of GetA0() explicitly indicate that answer is returned in D0,
- for better compatibility with Metrowerks CodeWarrior C.
- 10/27/94 dgp It is very annoying for the machine to crash anytime you try to quit your application
- by escaping via MacsBugs escape to shell. The problem is that CodeWarrior 4.5 doesn't attach
- the atexit() tasks to _EscapeToShell. So I do it here.
- 11/3/94 dgp In response to a bug report by David Brainard that after 128 calls to PeekTimer things
- got screwy I replicated the problem and discovered that I could cure it by clearing tmReserved
- in ResumeTimer below, though I have no idea why this is necessary.
- 11/4/94 dgp After discovering that the Radius Rocket lies, via Gestalt, claiming that
- the Extended Time Manager is present when it really isn't, I added code to check for
- the services of the Extended Time Manager, and adjust t->timeManagerVersion accordingly.
- This eliminates the crashes I was getting when running PeekTimer on the Radius Rocket.
- 11/5/94 dgp tested and debugged the code when running without the Extended Time Manager.
- 1/5/95 dgp made compatible with Universal Headers 2.
- */
- #include "VideoToolbox.h"
- #include <math.h> // NAN
- #if __powerc
- static pascal void TimerTask(Timer *t);
- #else
- static pascal void TimerTask(void);
- #endif
- void KillEveryTimer(void);
- static void PatchExitToShell(void);
-
- /* This is a copy for reference. Original is in VideoToolbox.h.
- struct Timer{
- TMTask time;
- long ourA5;
- long interval; // interval length (in negative µs)
- long elapsed; // extra time, (in -µs), beyond elapsedIntervals*interval
- long elapsedIntervals; // positive count
- long timeToStartTimer; // minimum time in µs
- long stopDelay; // µs from call to stop, re from call to start
- long timeManagerVersion;
- struct Timer *next,*previous; // doubly linked list of Timers
- };
- */
-
- static Timer defaultTimer,qTimer;
- static long vmPresent=0,pageSize=0;
- #define TASK_SIZE 1024 // Generous guess for size of routine
- static double TimerSecs(Timer *t);
- static void ResumeTimer(Timer *t);
- static long TimerMicroSecs(Timer *t);
- #if !defined(NewTimerProc)
- #define NewTimerProc(proc) (TimerProcPtr)proc
- #endif
-
- Timer *NewTimer(void)
- {
- static short firstTime=1;
- Timer *t,*tt;
- long j;
-
- if(firstTime){
- firstTime=0;
- qTimer.next=qTimer.previous=NULL;
- if(!MATLAB){
- #if __MWERKS__
- // In CW4.5 _atexit() only gets called if you quit via abort() or exit().
- PatchExitToShell();
- #else
- _atexit(KillEveryTimer);
- #endif
- }
- Gestalt(gestaltVMAttr,&vmPresent);
- vmPresent &= gestaltVMPresent;
- if(vmPresent)Gestalt(gestaltLogicalPageSize,&pageSize);
- t=&defaultTimer;
- t->ourA5 = SetCurrentA5();
- t->time.tmAddr = NewTimerProc(TimerTask);
- t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
- t->elapsedIntervals=t->elapsed=0;
- t->timeManagerVersion=0;
- Gestalt(gestaltTimeMgrVersion,&t->timeManagerVersion);
- if(t->timeManagerVersion>=gestaltExtendedTimeMgr){
- // Let's make sure the Extended Time Manager is really here.
- // Under System 7.1 the Radius Rocket claims it's present, but it's not.
- TMTask time;
-
- time.tmAddr = NULL;
- time.tmCount=time.tmWakeUp=time.tmReserved=0;
- InsXTime((QElemPtr)&time);
- PrimeTime((QElemPtr)&time,-100000000L); // interval of 100 s
- RmvTime((QElemPtr) &time);
- time.tmReserved=0; // undocumented, but necessary on some Macs
- InsXTime((QElemPtr)&time);
- PrimeTime((QElemPtr)&time,0); // resume interrupted interval
- RmvTime((QElemPtr) &time);
- // a tmCount of zero couldn't happen if the Extended Time Manager were present.
- if(time.tmCount==0)t->timeManagerVersion=gestaltRevisedTimeMgr;
- }
- switch(t->timeManagerVersion){
- case 0:
- case gestaltStandardTimeMgr:
- printf("NewTimer: old System lacks the Revised Time Manager. %s\n",__FILE__);
- return NULL;
- case gestaltRevisedTimeMgr:
- case gestaltExtendedTimeMgr:
- default:
- // The programs in Timer.c assume that t->interval<0
- t->interval = -10000000; // Set the timer interval to 10 s
- }
- t->next=NULL;
- t->previous=&qTimer;
- t->timeToStartTimer=t->stopDelay=0;
-
- // Measure timeToStartTimer and stopDelay offsets.
- t=NewTimer();
- StartTimer(t);
- t->timeToStartTimer=StopTimer(t);
- tt=NewTimer();
- StartTimer(t);
- StartTimer(tt);
- j=StopTimer(t);
- DisposeTimer(tt);
- t->stopDelay=2*t->timeToStartTimer-j;
- // The computed "cycle" interval will have stopDelay removed.
- // The user wishing to compute the "between" interval will be
- // subtracting the timeToStartTimer, so we should subtract
- // the stopDelay from that so the stopDelay cancels out when
- // the "between" time is computed.
- t->timeToStartTimer-=t->stopDelay;
- defaultTimer.timeToStartTimer=t->timeToStartTimer;
- defaultTimer.stopDelay=t->stopDelay;
- return t;
- }
- t=(Timer *)NewPtr(sizeof(Timer));
- if(t!=NULL){
- *t=defaultTimer;
- t->next=qTimer.next;
- if(t->next!=NULL)t->next->previous=t;
- qTimer.next=t;
- if(vmPresent){
- HoldMemory(t,sizeof(*t));
- if(t->time.tmAddr!=NULL)HoldMemory(t->time.tmAddr,TASK_SIZE);
- }
- if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
- else InsTime((QElemPtr)t);
- }
- return t;
- }
-
- void DisposeTimer(Timer *t)
- {
- if(t==NULL)return;
- RmvTime((QElemPtr)t);
- if(vmPresent){
- //UnholdMemory(t,sizeof(*t));
- if(qTimer.next==t && t->next==NULL && t->time.tmAddr!=NULL)
- UnholdMemory(t->time.tmAddr,TASK_SIZE);
- }
- t->previous->next=t->next;
- if(t->next!=NULL)t->next->previous=t->previous;
- DisposPtr((Ptr)t);
- }
-
- void StartTimer(Timer *t)
- {
- if(t==NULL)return;
- if(t->time.qType<0)StopTimer(t); // whoops, it's already active.
- PrimeTime((QElemPtr)t,t->interval);
- }
-
- static void ResumeTimer(Timer *t)
- {
- // I don't understand why, but it is important to clear tmReserved.
- // The Time Manager documentation only says that tmReserved should be zeroed
- // when you FIRST install your task, not EACH TIME you install it.
- // At least some computers (e.g. PowerMac) increment tmReserved each time PeekTimer is called,
- // and things get screwy if tmReserved>=128.
- t->time.tmReserved=0;
- if(t->timeManagerVersion>=gestaltExtendedTimeMgr){
- // The Extended Time Mgr (System 7) allows us to resume exactly where we left off
- // provided we don't clear t->time.tmWakeUp
- if(t->time.tmCount==0 || t->time.tmCount<0 && t->time.tmCount>-10000){
- // we're dangerously close to the end of this interval: skip to next interval
- t->elapsedIntervals++;
- InsXTime((QElemPtr)t);
- PrimeTime((QElemPtr)t,t->interval); // resume, skipping current interval's wakeup
- }else{
- InsXTime((QElemPtr)t);
- PrimeTime((QElemPtr)t,0); // resume interrupted interval
- }
- }else{
- // Before System 7 we must start a new interval
- t->elapsed+=t->interval;
- if(t->time.tmCount<0)t->elapsed-=t->time.tmCount;
- else t->elapsed+=t->time.tmCount*1000;
- if(t->elapsed<t->interval){
- // avoid overflow of t->elapsed
- t->elapsed-=t->interval;
- t->elapsedIntervals++;
- }
- t->time.qType=t->time.tmReserved=t->time.tmWakeUp=t->time.tmCount=0;
- InsTime((QElemPtr)t);
- PrimeTime((QElemPtr)t,t->interval); // resume timing
- }
- }
-
- static double TimerSecs(register Timer *t)
- {
- register double s;
-
- // add up the elapsed time plus the interval we're in, minus the time left in it
- s=-((double)(t->elapsedIntervals+1)*t->interval + t->elapsed);// µs
- if(t->time.tmCount>0) s-=t->time.tmCount*1000; // -µs until end of interval
- else s+=t->time.tmCount; // -µs until end of interval
- s-=t->stopDelay; // compute "cycle" time
- s*=0.000001; // µs to s
- return s;
- }
-
- static long TimerMicroSecs(register Timer *t)
- {
- register long microSecs;
-
- // add up the elapsed time plus the interval we're in, minus the time left in it
- microSecs=-((t->elapsedIntervals+1)*t->interval + t->elapsed);// µs
- if(t->time.tmCount>0) microSecs-=t->time.tmCount*1000; // -µs until end of interval
- else microSecs+=t->time.tmCount; // -µs until end of interval
- microSecs-=t->stopDelay; // compute "cycle" time
- return microSecs;
- }
-
- double PeekTimerSecs(Timer *t)
- {
- Timer tCopy,*tt=&tCopy;
-
- if(t==NULL)return NAN;
- RmvTime((QElemPtr) t);
- *tt=*t; // get the info and resume timing as quickly as possible
- ResumeTimer(t);
- return TimerSecs(tt);
- }
-
- long PeekTimer(Timer *t)
- {
- Timer tCopy,*tt=&tCopy;
-
- if(t==NULL)return 0;
- RmvTime((QElemPtr) t);
- *tt=*t; // get the info and resume timing as quickly as possible
- ResumeTimer(t);
- return TimerMicroSecs(tt);
- }
-
- long StopTimer(Timer *t) // Returns µs
- {
- long microSecs;
-
- if(t==NULL)return LONG_MIN;
- RmvTime((QElemPtr)t);
- microSecs=TimerMicroSecs(t);
-
- // Reinstall the Timer, to be ready for for another call to StartTimer()
- t->time.qType=t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
- t->elapsed=t->elapsedIntervals=0;
- if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
- else InsTime((QElemPtr)t);
-
- return microSecs;
- }
-
- double StopTimerSecs(Timer *t) // Returns secs
- {
- double s;
-
- if(t==NULL)return NAN;
- RmvTime((QElemPtr)t);
- s=TimerSecs(t);
-
- // Reinstall the Timer, to be ready for for another call to StartTimer()
- t->time.qType=t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
- t->elapsed=t->elapsedIntervals=0;
- if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
- else InsTime((QElemPtr)t);
-
- return s;
- }
-
- // KillEveryTimer turns off all our Timers before we quit. There is no need to
- // free the space since the system will do that automatically when the application terminates.
-
- void KillEveryTimer(void)
- {
- Timer *t;
- extern Timer qTimer;
-
- t=qTimer.next;
- while(t!=NULL){
- RmvTime((QElemPtr)t);
- if(vmPresent){
- UnholdMemory(t,sizeof(*t));
- if(t->time.tmAddr!=NULL)UnholdMemory(t->time.tmAddr,TASK_SIZE);
- }
- t=t->next;
- }
- }
-
- // On 68k machines the Revised & Extended Time managers set A1=&task.time before calling TimerTask
- // The code allowing access to globals is commented out because it is not needed here.
-
- #if (THINK_C || THINK_CPLUS)
- #pragma options(!profile) // it would be dangerous to call the profiler from here
- #endif
- #if !__powerc
- #pragma parameter __D0 GetA1
- long GetA1(void)=0x2009; // MOVE.L A1,D0
- #endif
-
- #if __powerc
- static pascal void TimerTask(Timer *t)
- #else
- static pascal void TimerTask(void) // Called at interrupt time
- #endif
- {
- #if !__powerc
- // long oldA5;
- Timer *t;
- t=(Timer *)GetA1();
- // oldA5 = SetA5(t->ourA5); // Reestablish A5 for global variables
- #endif
- PrimeTime((QElemPtr)t,t->interval); // Repeat the interval
- t->elapsedIntervals++;
- #if !__powerc
- // SetA5(oldA5); // Restore A5
- #endif
- }
-
- /*
- The following code, which seems to work on 68k and ppc,
- is based on examples that appeared in UseNet csmp-digest-v3-046
- by Kevin Bell (kbell@cs.utexas.edu) and Bill Hofmann (wdh@netcom.com)
- in response to a query by Steve Coy (stevec@jolt.mpx.com.au)
- */
-
- #if UNIVERSAL_HEADERS
- #if UNIVERSAL_HEADERS>1
- extern pascal void ExitToShell(void) ONEWORDINLINE(0xA9F4);
- #endif
- typedef UniversalProcPtr TrapAddressType;
- #else
- #define UniversalProcPtr ProcPtr
- #define NewRoutineDescriptor(a,b,c) a
- typedef long TrapAddressType;
- #endif
- static UniversalProcPtr oldExitToShellTrapAddress=NULL;
- static void PatchExitToShell(void);
- static pascal void MyExitToShell(void);
- #include <Traps.h> // _ExitToShell
-
- static void PatchExitToShell(void)
- {
- UniversalProcPtr myExitToShellUPP;
- if(oldExitToShellTrapAddress==NULL){
- myExitToShellUPP=NewRoutineDescriptor((ProcPtr)MyExitToShell,kPascalStackBased,GetCurrentISA());
- oldExitToShellTrapAddress=(UniversalProcPtr)GetToolTrapAddress(_ExitToShell);
- SetToolTrapAddress((TrapAddressType)myExitToShellUPP,_ExitToShell);
- }
- }
-
- static pascal void MyExitToShell(void)
- {
- SetCurrentA5();
- SetToolTrapAddress((TrapAddressType)oldExitToShellTrapAddress,_ExitToShell);
- KillEveryTimer();
- ExitToShell();
- }
-